﻿using System;
using System.Collections.Generic;
using System.Text;
using NHibernate;
using NCommet.Core;
using System.Reflection;

using System.Configuration;
using System.Data.SqlClient;
using System.Data;
using log4net;
using NCommet.Core.Services;
using NCommet.Core.Agents;

namespace NCommet.Modules.Dao
{
    /// <summary>
    /// It's the main implementation of <see cref="NCommet.Core.Agents.IPersister"/>. It uses NHibernate to store instances of <see cref="NCommet.Core.Item"/>, <see cref="NCommet.Core.RelItem"/> and <see cref="NCommet.Core.ContentBase"/>.
    /// </summary>
    /// <remarks>
    ///     <para>
    ///     For application configuration, see <see cref="NCommet.Modules.Dao.NHibernateSessionManager"/>.
    ///     </para>
    /// </remarks>
    public sealed class Storage : NCommet.Core.Agents.IPersister
    {
        private ILog mylog;
        private ILog MyLog { get { return mylog ?? (mylog = LogManager.GetLogger("NCommet.Modules.Dao")); } }

        private ISession session
        {
            get
            {
                return NHibernateSessionManager.Instance.GetSession();
            }
        }

        #region Transaction Support
        public void BeginTransaction()
        {
            NHibernateSessionManager.Instance.BeginTransaction();
        }

        public void RollbackTransaction()
        {
            NHibernateSessionManager.Instance.RollbackTransaction();
        }

        public void CommitTransaction()
        {
            NHibernateSessionManager.Instance.CommitTransaction();
        }
        #endregion

        #region Save

        private void SaveTransactionfull(object oObject)
        {
            try
            {
                NHibernateSessionManager.Instance.BeginTransaction();
                session.SaveOrUpdate(oObject);
                session.Flush();
                session.Refresh(oObject);
                NHibernateSessionManager.Instance.CommitTransaction();
            }
            catch (Exception ex)
            {
                NHibernateSessionManager.Instance.RollbackTransaction();
                throw ex;
            }
        }

        private void SaveTransactionless(object oObject)
        {
            session.SaveOrUpdate(oObject);
        }

        private void SaveObject(object oObject)
        {
            lock (this)
            {
                if (!NHibernateSessionManager.Instance.InTransaction())
                    SaveTransactionfull(oObject);
                else
                    SaveTransactionless(oObject);
            }
        }

        #endregion

        #region Delete

        private void DeleteRelItemsNoFlush(Item item, string relationType, bool participatesAsSubject,
            bool participatesAsObject)
        {
            object[] values;
            NHibernate.Type.IType[] types;

            string hql = "FROM RelItem ri WHERE ";
            if (participatesAsSubject)
            {
                if (participatesAsObject)
                    hql += " ( ri.owner=:owner OR ri.relative=:relative ) ";
                else
                    hql += " ri.owner=:owner ";
            }
            else
            {
                if (participatesAsObject)
                    hql += " ri.relative=:relative ";
                else
                    return; // no participation? no way!
            }

            if (relationType != null)
                hql += " AND ri.relationType=:relationType ";

            if (participatesAsObject && participatesAsSubject)
            {
                if (relationType != null)
                {
                    values = new object[] { item, item, relationType };
                    types = new NHibernate.Type.IType[] { NHibernateUtil.Entity(typeof(Item)), NHibernateUtil.Entity(typeof(Item)), NHibernateUtil.String };
                }
                else
                {
                    values = new object[] { item, item };
                    types = new NHibernate.Type.IType[] { NHibernateUtil.Entity(typeof(Item)), NHibernateUtil.Entity(typeof(Item)) };
                }
            }
            else
            {
                if (relationType != null)
                {
                    values = new object[] { item, relationType };
                    types = new NHibernate.Type.IType[] { NHibernateUtil.Entity(typeof(Item)), NHibernateUtil.String };
                }
                else
                {
                    values = new object[] { item };
                    types = new NHibernate.Type.IType[] { NHibernateUtil.Entity(typeof(Item)) };
                }
            }

            session.Delete(hql, values, types);
        }

        private void DeleteRelItemsRecursiveNoFlush(Item item, string relationType, bool participatesAsSubject,
            bool participatesAsObject)
        {
            DeleteRelItemsNoFlush(item, relationType, participatesAsSubject, participatesAsObject);
            foreach (Item child in item.Children)
                DeleteRelItemsRecursiveNoFlush(child, relationType, participatesAsSubject, participatesAsObject);
        }

        public void DeleteRelItems(Item item, string relationType, bool participatesAsSubject,
            bool participatesAsObject)
        {
            DeleteRelItemsNoFlush(item, relationType, participatesAsSubject, participatesAsObject);
            session.Flush();
        }

        public void DeleteRelItemsRecursive(Item item, string relationType, bool participatesAsSubject,
            bool participatesAsObject)
        {
            DeleteRelItemsRecursiveNoFlush(item, relationType, participatesAsSubject, participatesAsObject);
            session.Flush();
        }

        private void DeleteReverseRelItems(Item item)
        {
            DeleteRelItemsRecursive(item, null, false, true);
        }

        private void DeleteTransactionfull(object oObject)
        {
            try
            {
                MyLog.Debug("DeleteTransactionfull Begin");
                NHibernateSessionManager.Instance.BeginTransaction();
                if (oObject is Item)
                    DeleteReverseRelItems((Item)oObject);
                MyLog.Debug("DeleteTransactionfull Delete");
                session.Delete(oObject);
                //session.Flush();
                NHibernateSessionManager.Instance.CommitTransaction();
                MyLog.Debug("DeleteTransactionfull End");
            }
            catch (Exception ex)
            {
                NHibernateSessionManager.Instance.RollbackTransaction();
                throw ex;
            }
        }

        private void DeleteTransactionless(object oObject)
        {
            if (oObject is Item)
                DeleteReverseRelItems((Item)oObject);
            session.Delete(oObject);
        }

        private void DeleteObject(object oObject)
        {
            if (!NHibernateSessionManager.Instance.InTransaction())
                DeleteTransactionfull(oObject);
            else
                DeleteTransactionless(oObject);
        }

        #endregion

        #region IPersister Members

        public bool IsInitialized(object proxy)
        {
            return NHibernateUtil.IsInitialized(proxy);
        }

        public T Get<T>(int ItemID)
        {
            return (T)session.Get(typeof(T), ItemID);
        }

        public void Save(object item)
        {
            SaveObject(item);
        }

        private static void NotifyDaoEventsSinkAboutDelete(bool InsideDelete)
        {
            DaoInterceptor di = NCommetContainer.Instance.DaoEventsSink as DaoInterceptor;
            if (di != null)
                di.InsideDelete = InsideDelete;
        }

        public void Delete(object item)
        {
            try
            {
                NotifyDaoEventsSinkAboutDelete(true);
                DeleteObject(item);
            }
            finally
            {
                NotifyDaoEventsSinkAboutDelete(false);
            }
        }

        public IList<Item> GetRoots()
        {
            return session.CreateQuery("FROM Item it WHERE it.parent IS NULL").List<Item>();
        }

        public IList<Item> GetRoots(string ItemType)
        {
            return session.CreateQuery("FROM Item it WHERE it.parent IS NULL AND it.itemType=:itemType").SetString("itemType", ItemType).List<Item>();
        }

        public IList<Item> GetRoots(string[] ItemTypes)
        {
            return session.CreateQuery("FROM Item it WHERE it.parent IS NULL AND it.itemType IN (:itemTypes)").SetParameterList("itemTypes", ItemTypes).List<Item>();
        }


        public IList<RelItem> GetReverseRelItems(Item relative)
        {
            return session.CreateQuery("FROM RelItem ri WHERE ri.relative=:relative").SetFlushMode(FlushMode.Never).SetParameter("relative", relative).List<RelItem>();
        }

        public IList<Item> FilterChildren(Item parent, string ItemType)
        {
            return session.CreateQuery("FROM Item it WHERE it.parent=:parent AND it.itemType=:itemType ORDER BY it.order").SetFlushMode(FlushMode.Never).SetParameter("parent", parent).SetString("itemType", ItemType).List<Item>();
        }

        public IList<Item> FilterChildren(Item parent, string[] ItemTypes, string[] Tags)
        {
            string hql = "FROM Item it WHERE it.parent=:parent";
            if (ItemTypes != null && ItemTypes.Length > 0)
            {
                if (Tags != null && Tags.Length > 0)
                    hql += " AND it.itemType IN (:itemTypes) AND it.tags IN (:tags)";
                else
                    hql += " AND it.itemType IN (:itemTypes)";
            }
            else
            {
                if (Tags != null && Tags.Length > 0)
                    hql += " AND it.tags IN (:tags)";
            }
            hql += "  ORDER BY it.order";
            IQuery q = session.CreateQuery(hql).SetFlushMode(FlushMode.Never).SetParameter("parent", parent);
            if (ItemTypes != null && ItemTypes.Length > 0)
                q.SetParameterList("itemTypes", ItemTypes);
            if (Tags != null && Tags.Length > 0)
                q.SetParameterList("tags", Tags);
            return q.List<Item>();
        }

        public IList<Item> FilterChildren(Item parent, IEnumerable<string> ItemTypes)
        {
            string hql = "FROM Item it WHERE it.parent=:parent";
            hql += " AND it.itemType IN (:itemTypes)";
            hql += "  ORDER BY it.order";
            IQuery q = session.CreateQuery(hql).SetFlushMode(FlushMode.Never).SetParameter("parent", parent);
            q.SetParameterList("itemTypes", ItemTypes);
            return q.List<Item>();
        }

        public IList<RelItem> FilterRelItems(Item owner, string RelationType, string RelativeItemType)
        {
            string queryString = "FROM RelItem ri WHERE ri.owner=:owner";
            if (!string.IsNullOrEmpty(RelationType))
                queryString += " AND ri.relationType=:relationType";
            if (!string.IsNullOrEmpty(RelativeItemType))
                queryString += " AND ri.relative.itemType=:relativeItemType";

            IQuery q = session.CreateQuery(queryString).SetFlushMode(FlushMode.Never);
            q.SetParameter("owner", owner);
            if (!string.IsNullOrEmpty(RelationType))
                q.SetString("relationType", RelationType);
            if (!string.IsNullOrEmpty(RelativeItemType))
                q.SetString("relativeItemType", RelativeItemType);
            return q.List<RelItem>();
        }

        public IList<Item> GetAllItems()
        {
            return session.CreateQuery("FROM Item it").SetFlushMode(FlushMode.Never).List<Item>();
        }

        public IList<Item> GetItemsByName(string Name)
        {
            return session.CreateQuery("FROM Item it WHERE it.name=:name").SetFlushMode(FlushMode.Never).SetParameter("name", Name).List<Item>();
        }

        public IList<Item> GetItemsByItemType(string ItemType)
        {
            return session.CreateQuery("FROM Item it WHERE it.itemType LIKE :itemType").SetFlushMode(FlushMode.Never).SetParameter("itemType", ItemType).List<Item>();
        }

        public IList<Item> GetItemsByContent(string ItemType, string FieldName, string InputString, MatchingStringConditions condition)
        {
            //If FieldName or InputString is null throw an ArgumentException
            if (FieldName == null || InputString == null)
            {
                throw new ArgumentException("FieldName and Matching string should not be null.");
            }

            //Construct the matchingClause according to the condition. The matching clause looks for something like <FieldName>%InputString%</FieldName>
            //inside the serializedData.
            string matchingClause = "%<" + FieldName + ">";

            if (condition == MatchingStringConditions.STARTS_WITH)
            {
                matchingClause += InputString + "%";
            }
            else if (condition == MatchingStringConditions.CONTAINS)
            {
                matchingClause += "%" + InputString + "%";
            }
            else if (condition == MatchingStringConditions.ENDS_WITH)
            {
                matchingClause += "%" + InputString;
            }

            matchingClause += "</" + FieldName + ">%";

            //Construct the query
            StringBuilder selectQuery = new StringBuilder();

            selectQuery.Append("SELECT it FROM Item it WHERE it.content.SerializedData LIKE :matchingClause ");

            //If ItemType is not null add it to the query. Else search for every item type.
            if (ItemType != null)
            {
                selectQuery.Append("AND it.itemType=:itemType");
            }

            IQuery query = session.CreateQuery(selectQuery.ToString());
            query.SetParameter("matchingClause", matchingClause);

            //If ItemType is not null add it as a parameter to the query.
            if (ItemType != null)
            {
                query.SetParameter("itemType", ItemType);
            }

            //Return the list of the items that contain the InputString
            return query.SetFlushMode(FlushMode.Never).List<Item>();
        }

        public int GetItemCountByName(string Name)
        {
            object obj = session.CreateQuery("SELECT COUNT(*) FROM Item WHERE Name=:name").SetFlushMode(FlushMode.Never).SetParameter("name", Name).UniqueResult();
            return obj == null ? 0 : Convert.ToInt32(obj);
        }


        public bool IsTransient(Item item)
        {
            return item.Id <= 0;
        }

        public bool IsTransient(ContentBase contentBase)
        {
            return contentBase.Id <= 0;
        }

        public NCommet.Core.Agents.IPersisterMetadata GetPersisterMetadata()
        {
            return new StorageMetadata();
        }

        public void RequestExplicitSession()
        {
            NHibernateSessionManager.Instance.CloseSession();
        }

        #endregion

        #region IPersister Id manipulation Members

        public IList<int> SelectIds(int ParentId)
        {
            return session.CreateSQLQuery("SELECT id FROM item WHERE parent_id=" + ParentId + " ORDER BY SortOrder").AddScalar("id", NHibernateUtil.Int32).List<int>();
        }

        public IList<Item> GetItems(IList<int> Ids)
        {
            /*StringBuilder sb = new StringBuilder();
            bool first = true;
            foreach (int i in Ids)
            {
                if (first)
                    first = false;
                else
                    sb.Append(", ");
                sb.Append(i);
            }*/

            object[] oIds = new object[Ids.Count];
            for (int i = 0; i < oIds.Length; i++)
                oIds[i] = Ids[i];
            IList<Item> result = session.CreateCriteria(typeof(Item))
                .Add(new NHibernate.Expression.InExpression("id", oIds))
                .List<Item>();

            List<Item> sortedResult = new List<Item>(result.Count);
            for (int i = 0; i < Ids.Count; i++)
            {
                bool found = false;
                int j = -1;
                while (!found && ++j < result.Count)
                    if (result[j].Id == Ids[i])
                    {
                        sortedResult.Add(result[j]);
                        found = true;
                    }
            }
            return sortedResult;
        }
        #endregion
    }

    class StorageMetadata : NCommet.Core.Agents.IPersisterMetadata
    {
        #region IPersisterMetadata Members

        public int GetItemNameMaxLength()
        {
            return 250;
        }

        #endregion
    }
}
